#include "comdb.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <math.h>

/* cliente_db.c:  implementação do cliente */


//struct contendo as informacoes dos funcionarios contidas no banco de dados
typedef struct{
	char nome[30];
	char cpf[13];
	char telefone[13];
	char endereco[30];
	char dnasc[11];
} atributos_func;

//calculo da diferenca entre dois tempos: 'inicio' e 'fim'
struct timespec diferencaEntreInstantes(struct timespec inicio, struct timespec fim)
{
	struct timespec temp;
	if ((fim.tv_nsec-inicio.tv_nsec)<0) {
		temp.tv_sec = fim.tv_sec-inicio.tv_sec-1;
		temp.tv_nsec = 1000000000+fim.tv_nsec-inicio.tv_nsec;
	} else {
		temp.tv_sec = fim.tv_sec-inicio.tv_sec;
		temp.tv_nsec = fim.tv_nsec-inicio.tv_nsec;
	}

	return temp;
}

//calcula a media e desvio padrao dos tempos medidos em "Teste de Desempenho"
void calcula_med_e_desv(long int *amostras_tempo, float *media_t, float *desv_p, int num_amostras){

	int i, soma = 0;
	float soma_var;

	//soma o tempo total de uma dada operação (inserção, remoção ou consulta)
	for(i = 0; i < num_amostras; i++){

		soma += amostras_tempo[i];
	}
    //calcula a media baseada nos tempos enviados e no número de tempos enviados
	*media_t = soma/num_amostras;

	soma_var = 0;

    //calcula o desvio padrão baseado nos tempos próprios e no tempo médio das operações
	for(i = 0; i < num_amostras; i++){

		soma_var += pow((amostras_tempo[i] - *media_t),2);
	}
 	 *desv_p = sqrt(soma_var/(num_amostras - 1));
}

//formata o comando de inserção interpretado pelo SQL para ser usado de modo genérico
void formata_insercao(char *cmd_sql, atributos_func *atributos){

	//monta a string cmd_sql com a formatação correta usando os valores da struct 'atributos'
	strcpy(cmd_sql, "insert into funcionarios values('\0");

	strcat(cmd_sql, atributos->cpf);
	strcat(cmd_sql, "','\0");
	strcat(cmd_sql, atributos->nome);
	strcat(cmd_sql, "','\0");
	strcat(cmd_sql, atributos->telefone);
	strcat(cmd_sql, "','\0");
	strcat(cmd_sql, atributos->endereco);
	strcat(cmd_sql, "','\0");
	strcat(cmd_sql, atributos->dnasc);
	strcat(cmd_sql, "')\0");
}

char *comdbprog_1(char *servidor, char *cmd_sql, medidas *medidas_cliente, int op)
{
	CLIENT *clnt;
	char **resultado_1;

    //linka o cliente_db com o arquivo comdb_clnt gerado pelo RPCGEN
	clnt = clnt_create(servidor, COMDBPROG, COMDBVERS, "tcp");

    //caso não seja possível a criação do cliente
	if (clnt == NULL){
      		clnt_pcreateerror(servidor);
      		exit(1);
  	}

    //caso a chamada à comdbprog_l tenha vindo de uma função de acesso ao banco de dados
	if(op == 1){
		resultado_1 = comdb_1(&cmd_sql, clnt);
	}

    //caso a chamada à comdbprog_1 tenha vindo dos testes
	else
		resultado_1 = regmed_1(medidas_cliente, clnt);

    //caso a resposta do servidor falhe
   	if (resultado_1 == NULL) {
      		clnt_perror(clnt, "Chamada falhou.");
   	}

    clnt_destroy(clnt);

	return *resultado_1;
}

//exibe os resultados na tela de forma padronizada: Exibe um espaço entre os campos e um enter entre as tuplas
void mostra_resultado(char *resultado){

	char buffer;
	int i;

	for(i = 0; i < strlen(resultado); i++){

		buffer = resultado[i];

		if(buffer == '|')
			printf(" ");

		else if(buffer == '#')
			printf("\n");

		else
			printf("%c", buffer);
	}
}


void requisita_consulta(char *servidor){

	char *cmd_sql, op = '4', temp[30], *resultado;

	cmd_sql = (char*)malloc(100*sizeof(char));
	cmd_sql[0] = '\0';

	scanf("%*c");

	while(op != '0'){

		printf("\e[H\e[2J");

		printf("******** Cliente **********\n");

		printf("	MENU CONSULTA		\n");
		printf("Selecione uma opção: \n 1)Consultar a base completa \n 2)Consultar por CPF \n 3)Consultar por nome \n 0)Voltar ao menu principal \n\n Opcao escolhida: ");

		op = '4';
		scanf("%c", &op);

		switch(op){

			//armazena em cmd_sql o comando de consultar toda base de dados
			case '1' : strcpy(cmd_sql, "select * from funcionarios\0");
			break;

            //armazena em cmd_sql o comando de consultar na base de dados as tuplas que constam o CPF buscado
			case '2': printf(" Entre com o CPF desejado: ");
                      scanf("%s", temp);
                      strcpy(cmd_sql, "select * from funcionarios where cpf = \0");
                      strcat(cmd_sql, temp);
			break;

            //armazena em cmd_sql o comando de consultar na base de dados as tuplas que comecem com o nome buscado
			case '3': printf(" Entre com o nome desejado: ");
                      scanf("%*c");
			          gets(temp);
                      strcpy(cmd_sql, "select * from funcionarios where nome like '\0");
                      strcat(cmd_sql, temp);
                      strcat(cmd_sql, "%'\0");
			break;

            //retorna ao menu principal
			case '0': return;
			break;

			default: printf("\n Opcao invalida");
			break;
		}

        //caso uma das opções 1, 2 ou 3 seja escolhida, envia o comando para o server pelo RPC e exibe o resultado
		if(op == '1' || op == '2' || op == '3'){

	        	resultado = comdbprog_1(servidor, cmd_sql, NULL, 1);
	        	mostra_resultado(resultado);
		}

		getchar();
		scanf("%*c");
	}
}

void requisita_remocao(char *servidor){

	char *cmd_sql, *resultado, temp[20], op = '0';

	cmd_sql = (char*)malloc(100*sizeof(char));
	cmd_sql[0] = '\0';

    //requisita e obtem, do usuario, o CPF do funcionario a ser removido
	printf(" Entre com o CPF do funcionario a ser excluido: ");
	scanf("%s", temp);
	strcpy(cmd_sql, "select * from funcionarios where cpf = \0");
	strcat(cmd_sql, temp);

	resultado = comdbprog_1(servidor, cmd_sql, NULL, 1);
	mostra_resultado(resultado);

    //verifica se o CPF é valido e, se sim, pede confirmação de exclusão
	if(strcmp(resultado, " Nenhuma entrada encontrada.\0")){

		printf("\n Tem certeza de que deseja excluir o funcionario acima? (s/n): ");
		scanf("%*c");
		scanf("%c", &op);

        //caso a entrada seja 'n', cancela a exclusão de funcionário
		if(op == 'n'){

			printf(" Exclusao cancelada.");
		}

        //caso a entrada seja 's', formata cmd_sql com o comando de exlusão e o envia ao servidor
		else if(op == 's'){

			strcpy(cmd_sql, "delete from funcionarios where cpf = \0");
			strcat(cmd_sql, temp);
			resultado = comdbprog_1(servidor, cmd_sql, NULL, 1);
            mostra_resultado(resultado);
		}

        //caso a entrada seja diferente de 's' ou 'n', cancela a operação
		else
			printf(" Opcao invalida. Exclusao cancelada");

	}

	getchar();
	scanf("%*c");

	return;
}

void requisita_insercao(char *servidor){

	char temp[30], *resultado, *cmd_sql;
	atributos_func atributos;

	printf("\e[H\e[2J");
	printf("******** Cliente **********\n");
	printf("	MENU INSERCAO		\n");

	cmd_sql = (char*)malloc(100*sizeof(char));
	cmd_sql[0] = '\0';

    //requisita e obtem, do usuario, as informações do novo funcionario
	printf("Entre com o nome do novo funcionario: ");
	scanf("%*c");
	gets(atributos.nome);

	printf("Entre com o CPF do novo funcionario: ");
	scanf("%s", atributos.cpf);

	printf("Entre com o telefone do novo funcionario: ");
	scanf("%s", atributos.telefone);

	printf("Entre com o endereco do novo funcionario: ");
	scanf("%s", atributos.endereco);

	printf("Entre com a data de nascimento do novo funcionario(aaaa-mm-dd): ");
	scanf("%s", atributos.dnasc);

    //atribui à cmd_sql o comando interpretado pelo sql para a inserção do novo funcionario
	formata_insercao(cmd_sql, &atributos);

    //envia o comando de inserção via RPC ao servidor e exibe os resultados
	resultado = comdbprog_1(servidor, cmd_sql, NULL, 1);
	mostra_resultado(resultado);

	getchar();
	scanf("%*c");
}


void testa_insercao(char *servidor, long int *amostras_tempo, char *nome_host){

	int i, tempcpf = 100000000;
	atributos_func atributos;
	char *cmd_sql, buf[15];
	struct timespec tinic, tfinal;

	cmd_sql = (char*)malloc(100*sizeof(char));
	cmd_sql[0] = '\0';

	strcpy(atributos.nome, "NomeTeste\0");
	strcpy(atributos.telefone, "(11)12345678\0");
	strcpy(atributos.endereco, "EnderecoTeste\0");
	strcpy(atributos.dnasc, "2012-12-21\0");

	for(i = 0; i < 10; i++){
        //atribui os dois primeiros caracteres do nome do host ao cpf do funcionário, sua chave primária, para que seja possível que inserções simultâneas sejam realizadas por múltiplos clientes
		atributos.cpf[0] = nome_host[0];
		atributos.cpf[1] = nome_host[1];
		atributos.cpf[2] = '\0';
		sprintf(buf, "%d", tempcpf);
		strcat(atributos.cpf, buf);

        //atribui a cmd_sql o comando de inserção de funcionario baseada nas informações da struct criadas acima
		formata_insercao(cmd_sql, &atributos);

        //captura o tempo inicial
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);

		//enviará ao servidor a requisição de inserção
		comdbprog_1(servidor, cmd_sql, NULL, 1);

		//captura o tempo final
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

		//armazena o tempo levado pelo processo de inserção
		amostras_tempo[i] = (long int)diferencaEntreInstantes(tinic, tfinal).tv_nsec;

		tempcpf++;
	}
}

void testa_remocao(char *servidor, long int *amostras_tempo, char *nome_host){

	int i, tempcpf = 100000000;
	char *cmd_sql, buf[15], cpf[13];
	struct timespec tinic, tfinal;

	cmd_sql = (char*)malloc(100*sizeof(char));
	cmd_sql[0] = '\0';

	for(i = 0; i < 10; i++){

		cpf[0] = nome_host[0];
		cpf[1] = nome_host[1];
		cpf[2] = '\0';

		sprintf(buf, "%d", tempcpf);
		strcat(cpf, buf);

		//armazena em cmd_sql o comando de remoção
		strcpy(cmd_sql, "delete from funcionarios where cpf = '\0");
		strcat(cmd_sql, cpf);
		strcat(cmd_sql, "'\0");

        	//captura o tempo inicial
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);

		//encaminha ao servidor a requisição de remoção
		comdbprog_1(servidor, cmd_sql, NULL, 1);

		//captura o tempo final
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

		//captura o tempo total da operação de remoção
		amostras_tempo[i] = (long int)diferencaEntreInstantes(tinic, tfinal).tv_nsec;

		tempcpf++;
	}
}

void testa_consulta(char *servidor, long int *amostras_tempo, char *nome_host){

	int i, tempcpf = 100000000;
	char *cmd_sql, buf[15], cpf[13];
	struct timespec tinic, tfinal;

	cmd_sql = (char*)malloc(100*sizeof(char));
	cmd_sql[0] = '\0';

	for(i = 0; i < 80; i++){

		cpf[0] = nome_host[0];
		cpf[1] = nome_host[1];
		cpf[2] = '\0';

		sprintf(buf, "%d", tempcpf);
		strcat(cpf, buf);

		//armazena em cmd_sql o comando de consulta
		strcpy(cmd_sql, "select * from funcionarios where cpf = '\0");
		strcat(cmd_sql, cpf);
		strcat(cmd_sql, "'\0");

        	//captura o tempo inicial
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);

		//encaminha ao servidor a requisição de consulta
		comdbprog_1(servidor, cmd_sql, NULL, 1);

		//captura o tempo final
		clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

		//captura o tempo total da operação de consulta
		amostras_tempo[i] = (long int)diferencaEntreInstantes(tinic, tfinal).tv_nsec;

		tempcpf = 100000000 + (i + 1)%10;
	}

}

void testa_desempenho(char *servidor){

	float media_t_ins, media_t_cons, media_t_rem, desv_p_ins, desv_p_cons, desv_p_rem;
	long int amostras_tempo[80];
	medidas medidas_cliente;
	char *nome_host, *resultado;
	struct timespec tinic, tfinal;

	nome_host = (char*)malloc(15*sizeof(char));
	gethostname(nome_host, 15);

	//os testes de inserção, consulta e remoção são realizados sequencialmente. A base de dados é deixada como estava antes, ao final do processo de avaliação. O teste específico para cada operação (inserção, consulta e remoção) é responsável por registrar as amostras de tempo para as múltiplas execuções da operação. Aqui, afere-se o instante inicial da sequência de testes
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tinic);
	
	testa_insercao(servidor, amostras_tempo, nome_host);
	testa_consulta(servidor, amostras_tempo, nome_host);
	testa_remocao(servidor, amostras_tempo, nome_host);

	//registra-se o instante final da sequência de testes
	clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &tfinal);

	//cálculos das médias e dos desvios padrões, com base nas amostras de tempo colhidas
	calcula_med_e_desv(amostras_tempo, &media_t_cons, &desv_p_cons, 80);
	calcula_med_e_desv(amostras_tempo, &media_t_ins, &desv_p_ins, 10);
	calcula_med_e_desv(amostras_tempo, &media_t_rem, &desv_p_rem, 10);

	medidas_cliente.nome_cliente = nome_host;

	//é preparada a estrutura com as informações de tempo que serão repassadas ao servidor para que sejam registradas no arquivo de log
	medidas_cliente.med_ins = media_t_ins;
	medidas_cliente.desv_ins = desv_p_ins;
	medidas_cliente.med_cons = media_t_cons;
	medidas_cliente.desv_cons = desv_p_cons;
	medidas_cliente.med_rem = media_t_rem;
	medidas_cliente.desv_rem = desv_p_rem;
	//abaixo é registrado o tempo total consumido na sequência de testes
	medidas_cliente.tempo_total_segundos = (long int)diferencaEntreInstantes(tinic, tfinal).tv_sec;
	medidas_cliente.tempo_total_nsegundos = (long int)diferencaEntreInstantes(tinic, tfinal).tv_nsec;

	//recebimento da confirmação enviada pelo servidor
	resultado = comdbprog_1(servidor, NULL, &medidas_cliente, 2);
	mostra_resultado(resultado);

	getchar();
	scanf("%*c");
}


int main( int argc, char* argv[] )
{
	char *servidor, op = '7';

	if(argc != 2) {

		exit(1);
	}

    //o nome do servidor é obtido a partir do argumento enviado na execução do cliente
	servidor = argv[1];

    //implementa o menu de seleção inicial, onde a opção '0' encerra a aplicação
	while(op != '0'){

		printf("\e[H\e[2J");

		printf("******** Cliente **********\n");

		op = '7';

		printf("	MENU PRINCIPAL		\n");
		printf("Selecione uma opção: \n 1)Realizar consulta \n 2)Realizar insercao \n 3)Realizar remocao \n 4)Realizar teste de desempenho \n 0)Sair \n\n Opcao escolhida: ");
		scanf("%c", &op);

		switch(op){

			case '1': requisita_consulta(servidor);
			break;

			case '2': requisita_insercao(servidor);
			break;

			case '3': requisita_remocao(servidor);
			break;

			case '4': testa_desempenho(servidor);
			break;

			case '0': printf("\n Operacao encerrada. Todas as alteracoes foram salvas.\n");
			break;

			default: printf("\n Opcao invalida"); getchar();
			break;
		}
	}

	exit(0);
}
